;
; CGAG.ASM  Fast Graphics for CGA
;
; (C) 3/9/88  A.Key
;
;...sincludes:0:
		INCLUDE	INTDOS.INC
		INCLUDE	INTVID.INC
;...e
;...sequates:0:
seg_cga		EQU	0B800H
even_step	EQU	2000H
odd_step	EQU	-2000H+80

max_sprite	EQU	100
bytes_per_spr	EQU	128
;...e
;...sdata_seg:0:
data_seg	SEGMENT	'data'

		DB	16384 DUP (0)	; must be first, holds masks

t_plot		EQU	0
t_line		EQU	1
t_diag_vert	EQU	2
t_vert_diag	EQU	3
t_rect		EQU	4

jmp_tab		DW	OFFSET mask_plot
		DW	OFFSET mask_line
		DW	OFFSET mask_diag_vert
		DW	OFFSET mask_vert_diag
		DW	OFFSET mask_rect

item		STRUC

item_type	DB	?		; t_plot, t_line etc.
item_dx		DB	?
item_dy		DB	?
item_dz		DB	?		; distance coefficients
item_min_x	DW	?
item_max_x	DW	?
item_min_y	DW	?
item_max_y	DW	?
item_x1		DW	?
item_y1		DW	?
item_x2		DW	?
item_y2		DW	?

item		ENDS

max_items	EQU	250
item_ptr	DW	OFFSET item_table

item_table	EQU	$
REPT		max_items
		item	<>
ENDM

sccs_id		DB	'@(#)Fast CGA graphic routines  3/9/88', 0

font_fn		DB	'chars.cf',0
charset		DW	6 DUP (?)	; the header
char_data	DB	16 * (176-32) DUP (?)

sprite_fn	DB	'sprites.cs',0
sprtab		DW	3 DUP (?)	; header
spr_data	DB	max_sprite * bytes_per_spr DUP (?)

shifted_data	DB	5 * 16 + 5 * 16 DUP (?)

precalc		STRUC

precalc_d	DW	?		; distance factor x
precalc_x	DW	?		; screen x
precalc_y	DW	?		; screen y
precalc_spos	DW	?		; destination screen address
precalc_sseg	DW	?		; destination screen segment
precalc_mask	DW	5 * 16 DUP (?)	; combined mask

precalc		ENDS

max_precalc	EQU	50
precalc_ptr	DW	OFFSET precalc_table

precalc_table	EQU	$
REPT		max_precalc
		precalc	<>
ENDM

refresh		STRUC

refresh_x	DW	?
refresh_y	DW	?
refresh_addr	DW	?

refresh		ENDS

max_refresh	EQU	2 * max_precalc
refresh_ptr	DW	OFFSET refresh_table

refresh_table	EQU	$
REPT		max_refresh
		refresh	<>
ENDM

prev_x		DW	?
prev_y		DW	?

data_seg	ENDS
;...e
;...spage_1_seg:0:
page_1_seg	SEGMENT	'data'

		DB	16384 DUP (0)	; alternate screen copy

page_1_seg	ENDS
;...e
;...spage_2_seg:0:
page_2_seg	SEGMENT	'data'

		DB	16384 DUP (0)	; alternate screen copy

page_2_seg	ENDS
;...e
;...sCGAG_TEXT:0:
CGAG_TEXT	SEGMENT PUBLIC 'code'

		ASSUME	CS:CGAG_TEXT, DS:data_seg

;...spublics:0:
		PUBLIC	g_init
		PUBLIC	g_deinit

		PUBLIC	g_s_print

		PUBLIC	g_s_plot
		PUBLIC	g_s_line
		PUBLIC	g_s_unline
		PUBLIC	g_s_diag_vert
		PUBLIC	g_s_vert_diag
		PUBLIC	g_s_rect

		PUBLIC	g_s_update
		PUBLIC	g_s_clear
		PUBLIC	g_s_copy

		PUBLIC	g_l_plot
		PUBLIC	g_l_line
		PUBLIC	g_l_diag_vert
		PUBLIC	g_l_vert_diag
		PUBLIC	g_l_rect

		PUBLIC	g_clear_log

		PUBLIC	g_clear_spr
		PUBLIC	g_precalc
		PUBLIC	g_sprites
		PUBLIC	g_unsprite
		PUBLIC	g_refresh

		PUBLIC	g_sync
;...e
;...svars:0:
old_video_mode	DB	?
visible_page	DB	0
update_page	DB	0

last_filled	DW	?
x_lim		DW	?

bitmask		DB	?		; this block of vars used by line_guts
step_always	DW	?
step_sometimes	DW	?
plotter		DW	?
current_x	DW	?
current_y	DW	?
;...e

;...sBOOLEAN pascal g_init\40\\41\:0:
;
; BOOLEAN pascal g_init()
;
g_init		PROC	FAR

		PUSH	DS
		MOV	AX,SEG data_seg
		MOV	DS,AX

		MOV	DX,OFFSET font_fn
		MOV	AL,0
		DOSCALL	dos_open_fh
		JC	g_init_error
		PUSH	AX
		MOV	BX,AX
		MOV	CX,6*2 + (176-32)*16
		MOV	DX,OFFSET charset
		DOSCALL	dos_read_fh
		POP	BX
		DOSCALL	dos_close_fh

		MOV	DX,OFFSET sprite_fn
		MOV	AL,0
		DOSCALL	dos_open_fh
		JC	g_init_error
		PUSH	AX
		MOV	BX,AX
		MOV	CX,3*2 + max_sprite * bytes_per_spr
		MOV	DX,OFFSET sprtab
		DOSCALL	dos_read_fh
		POP	BX
		DOSCALL	dos_close_fh

		POP	DS

		VIDCALL	vid_get_mode
		MOV	CS:[old_video_mode],AL
		MOV	AL,4
		VIDCALL	vid_set_mode

		MOV	AX,1
		RET

g_init_error:	POP	DS
		XOR	AX,AX
		RET

g_init		ENDP
;...e
;...svoid pascal g_deinit\40\\41\:0:
;
; void pascal g_deinit()
;
g_deinit	PROC	FAR

		MOV	AL,CS:[old_video_mode]
		VIDCALL	vid_set_mode
		RET

g_deinit	ENDP
;...e

;...svoid pascal g_s_print\40\string\44\ x\44\ y\44\ col\41\:0:
;
; void pascal g_s_print(string, x, y, col)
;
; x and y are given in character positions, not pixels
;
string		= 12
x		= 10
y		= 8
col		= 6
;
g_s_print	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES

		MOV	CX,[BP+x]
		MOV	DX,[BP+y]
		MOV	BL,CS:[update_page]
		CALL	calc_page_seg	; BX = segment
		CALL	calc_char_addr	; ES:DI -> screen
		MOV	BX,[BP+col]
		MOV	SI,[BP+string]

g_s_print_next:	LODSB
		AND	AL,AL
		JZ	SHORT g_s_print_done
		PUSH	DS
		MOV	CX,SEG data_seg
		MOV	DS,CX
		PUSH	SI
		XOR	AH,AH
		SUB	AL,' '
		SHL	AX,1
		SHL	AX,1
		SHL	AX,1
		SHL	AX,1
		MOV	SI,OFFSET char_data
		ADD	SI,AX
		MOV	CX,4
g_s_print_loop:	LODSW
		NOT	AX
		AND	ES:[DI],AX
		NOT	AX
		AND	AH,BL
		AND	AL,BL	
		OR	ES:[DI],AX
		ADD	DI,2000H
		LODSW
		NOT	AX
		AND	ES:[DI],AX
		NOT	AX
		AND	AH,BH
		AND	AL,BH	
		OR	ES:[DI],AX
		ADD	DI,-2000H+80
		LOOP	g_s_print_loop
		ADD	DI,-4*80+2
		POP	SI
		POP	DS
		JMP	g_s_print_next

g_s_print_done:	POP	ES
		POP	BP
		RET	8

g_s_print	ENDP
;...e

;...svoid pascal g_s_plot\40\x\44\ y\44\ col\41\:0:
;
; void pascal g_s_plot(x, y, col)
;
x		= 10
y		= 8
col		= 6
;
g_s_plot	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		MOV	CX,[BP+x]
		MOV	DX,[BP+y]
		MOV	AX,[BP+col]
		PUSH	ES
		CALL	plot_guts
		POP	ES
		POP	BP
		RET	6

g_s_plot	ENDP
;...e
;...svoid pascal g_s_line\40\x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_s_line(x1, y1, x2, y2, col)
;
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_s_line	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES
		MOV	AX,OFFSET plot
		CALL	line_guts
		POP	ES
		POP	BP
		RET	10

g_s_line	ENDP
;...e
;...svoid pascal g_s_unline\40\x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_s_unline(page_from, x1, y1, x2, y2, col)
;
page_from	= 16
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_s_unline	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES
		PUSH	DS
		MOV	BX,[BP+page_from]
		CALL	calc_page_seg
		MOV	DS,BX
		MOV	AX,OFFSET unplot
		CALL	line_guts
		POP	DS
		POP	ES
		POP	BP
		RET	12

g_s_unline	ENDP
;...e
;...svoid pascal g_s_diag_vert\40\x\44\ x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_s_diag_vert(x1, y1, x2, y2, x3, y3, x, col)
;
x		= 16
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_s_diag_vert	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES
		MOV	AX,[BP+x]
		MOV	CS:[x_lim],AX
		MOV	CS:[last_filled],-1
		MOV	AX,OFFSET plot_and_line
		CALL	line_guts
		POP	ES
		POP	BP
		RET	12

g_s_diag_vert	ENDP
;...e
;...svoid pascal g_s_vert_diag\40\x\44\ x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_s_vert_diag(x1, y1, x2, y2, x3, y3, x, col)
;
x		= 16
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_s_vert_diag	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES
		MOV	AX,[BP+x]
		MOV	CS:[x_lim],AX
		MOV	CS:[last_filled],-1
		MOV	AX,OFFSET line_and_plot
		CALL	line_guts
		POP	ES
		POP	BP
		RET	12

g_s_vert_diag	ENDP
;...e
;...svoid pascal g_s_rect\40\x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_s_rect(x1, y1, x2, y2, col)
;
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_s_rect	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES

		MOV	CX,[BP+x1]	; ensure x1 < x2
		MOV	AX,[BP+x2]
		CMP	CX,AX
		JC	g_s_rect_x_ok
		XCHG	AX,CX
		MOV	[BP+x1],CX
		MOV	[BP+x2],AX
g_s_rect_x_ok:
		MOV	DX,[BP+y1]	; ensure y1 < y2
		MOV	AX,[BP+y2]
		CMP	DX,AX
		JC	g_s_rect_y_ok
		XCHG	AX,DX
		MOV	[BP+y1],DX
		MOV	[BP+y2],AX
g_s_rect_y_ok:
		CALL	rect_guts
		POP	ES
		POP	BP
		RET	10

g_s_rect	ENDP
;...e

;...svoid pascal g_s_update\40\page\41\:0:
;
; void pascal g_s_update(page)
;
g_s_update	PROC	FAR

		POP	DX
		POP	CX
		POP	AX		; AL = page
		PUSH	CX
		PUSH	DX
		MOV	CS:[update_page],AL
		RET

g_s_update	ENDP
;...e
;...svoid pascal g_s_clear\40\col\41\:0:
;
; void pascal g_s_clear(col)
;
g_s_clear	PROC	FAR

		POP	DX
		POP	CX
		POP	AX
		PUSH	CX
		PUSH	DX
		PUSH	ES
		MOV	BL,CS:[update_page]
		CALL	calc_page_seg
		MOV	ES,BX
		MOV	BL,AH		; BL = odd line colour
		MOV	AH,AL		; AX = even line colour
		XOR	DI,DI		; start with even scan lines
		MOV	CX,100 * 40	; 100 lines * 40 words / line
		REP	STOSW		; fill
		MOV	AH,BL
		MOV	AL,BL		; AX = odd line colour
		MOV	DI,02000H	; now do odd scan lines
		MOV	CX,100 * 40	; 100 lines * 40 words /line
		REP	STOSW		; fill
		POP	ES
		RET

g_s_clear	ENDP
;...e
;...svoid pascal g_s_copy\40\page_from\44\ page_to\41\:0:
;
; void pascal g_s_copy(page_from, page_to)
;
page_from	= 8
page_to		= 6
;
g_s_copy	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	DS
		PUSH	ES
		MOV	BX,[BP+page_from]
		CALL	calc_page_seg
		MOV	DS,BX
		MOV	BX,[BP+page_to]
		CALL	calc_page_seg
		MOV	ES,BX
		CALL	copy_screen
		POP	ES
		POP	DS
		POP	BP
		RET	4

g_s_copy	ENDP
;...e

;...scopy_screen:0:
;
; Copy a screens, worth of data from DS:0 to ES:0
;
copy_screen	PROC	NEAR

		XOR	SI,SI
		XOR	DI,DI
		MOV	CX,4000
		REP	MOVSW		; copy even lines
		MOV	SI,8192
		MOV	DI,SI		; skip the gap
		MOV	CX,4000
		REP	MOVSW		; copy odd lines
		RET

copy_screen	ENDP
;...e
;...scalc_page_seg:0:
;
; Get the correct segment loaded up into ES
;
; Input		BL = segment required
; Output	BX = segment of page
;
calc_page_seg	PROC	NEAR

		AND	BL,BL
		JZ	cps_2
		DEC	BL
		JZ	cps_4
		MOV	BX,SEG page_2_seg
		RET

cps_2:		MOV	BX,seg_cga
		RET

cps_4:		MOV	BX,SEG page_1_seg
		RET

calc_page_seg	ENDP
;...e
;...splot_guts:0:
;
; The actual guts of plotting
;
; Input:	(CX,DX) = coordinates, AX = colour
;
plot_guts	PROC	NEAR

		MOV	BL,CS:[update_page]
		CALL	calc_page_seg	; BX = segment
		CALL	calc_addr	; ES:DI -> address, AX set ok
					; CX still = x

		AND	CL,003H		; number of times to shift colour
					; and mask right
		SHL	CL,1

		MOV	CH,0C0H		; mask for plotting
		SHR	CH,CL		; positioned in correct place
		AND	AL,CH		; get colour
		NOT	CH		; complement to form inverse mask
		AND	ES:[DI],CH	; mask out existing colour
		OR	ES:[DI],AL	; add in the new colour
		RET

plot_guts	ENDP
;...e
;...sline_guts:0:
;
; The main Bresenham line drawing algorithm
;
; Input:	AX --> plotting routine
;		BP --> a frame holding vars below
;
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
; Uses:		ES:DI -> address, CH = positive mask, AX = col
;
line_guts	PROC	NEAR

		MOV	CS:[plotter],AX

; calculate screen position

		MOV	CX,[BP+x1]
		MOV	CS:[current_x],CX
		MOV	DX,[BP+y1]
		MOV	CS:[current_y],DX
		MOV	AX,[BP+col]
		MOV	BL,CS:[update_page]
		CALL	calc_page_seg	; BX = segment
		CALL	calc_addr	; ES:DI -> address, AX = colour
					; CX = x still

		AND	CL,003H
		SHL	CL,1
		MOV	CH,0C0H
		SHR	CH,CL
		MOV	CS:[bitmask],CH

; set up stepping for the right directions

		MOV	DX,[BP+x1]
		SUB	DX,[BP+x2]
		JG	line_2		; jump if x1 > x2
		NEG	DX
		MOV	SI,OFFSET step_right
		JMP	SHORT line_4
line_2:		MOV	SI,OFFSET step_left
line_4:
					; DX = |xdiff|
					; SI = horizontal stepper

		MOV	BX,[BP+y1]
		SUB	BX,[BP+y2]
		JG	line_6		; jump if y1 > y2
		NEG	BX
		MOV	CX,OFFSET step_down
		JMP	SHORT line_8
line_6:		MOV	CX,OFFSET step_up
line_8:
					; BX = |ydiff|
					; CX = vertical stepper

; determine whether x or y changes more rapidly

		CMP	DX,BX
		JG	line_20		; jump if x varys most
		XCHG	BX,DX		; DX = major side, BX = minor side
		XCHG	CX,SI		; SI = always stepper
					; CX = sometimes stepper

line_20:	MOV	CS:[step_always],SI
		MOV	CS:[step_sometimes],CX

		CALL	CS:[plotter]
		XOR	SI,SI		; clear total
		MOV	CX,DX
		JCXZ	line_30		; line is of zero length
line_24:	ADD	SI,BX
		SAL	SI,1
		CMP	SI,DX
		JNG	line_26
		SAR	SI,1
		SUB	SI,DX
		CALL	CS:[step_sometimes]
		JMP	SHORT line_28
line_26:	SAR	SI,1
line_28:	CALL	CS:[step_always]
		CALL	CS:[plotter]
		LOOP	line_24

line_30:	RET

line_guts	ENDP
;...e
;...splot:0:
;
; Plotting utility designed for use with line drawer
;
plot		PROC	NEAR

		PUSH	AX
		MOV	AH,CS:[bitmask]
		AND	AL,AH
		NOT	AH
		AND	ES:[DI],AH
		OR	ES:[DI],AL
		POP	AX
		RET

plot		ENDP
;...e
;...sunplot:0:
;
; Plotting utility designed for use with line drawer
;
unplot		PROC	NEAR

		MOV	AL,[DI]
		MOV	ES:[DI],AL
		RET

unplot		ENDP
;...e
;...splot_and_line:0:
;
; Plotter with option of drawing a line to a column for line drawer
;
plot_and_line	PROC	NEAR

		PUSH	AX
		PUSH	DX
		MOV	DX,CS:[current_y]
		CMP	DX,CS:[last_filled]
		JNZ	pal_do_it
		MOV	AH,CS:[bitmask]
		AND	AL,AH
		NOT	AH
		AND	ES:[DI],AH
		OR	ES:[DI],AL
		POP	DX
		POP	AX
		RET

pal_do_it:	MOV	CS:[last_filled],DX
		PUSH	DI
		PUSH	CX
		MOV	CX,CS:[current_x]
		MOV	DX,CS:[x_lim]
		SUB	DX,CX
		INC	DX
		MOV	CH,CS:[bitmask]
		CALL	do_hline_fwd
		POP	CX
		POP	DI
		POP	DX
		POP	AX
		RET

plot_and_line	ENDP
;...e
;...sline_and_plot:0:
;
; Plotter with option of drawing a line from a column for line drawer
;
line_and_plot	PROC	NEAR

		PUSH	AX
		PUSH	DX
		MOV	DX,CS:[current_y]
		CMP	DX,CS:[last_filled]
		JNZ	lap_do_it
		MOV	AH,CS:[bitmask]
		AND	AL,AH
		NOT	AH
		AND	ES:[DI],AH
		OR	ES:[DI],AL
		POP	DX
		POP	AX
		RET

lap_do_it:	MOV	CS:[last_filled],DX
		PUSH	DI
		PUSH	CX
		MOV	CX,CS:[current_x]
		MOV	DX,CS:[x_lim]
		SUB	DX,CX
		NEG	DX
		INC	DX
		MOV	CH,CS:[bitmask]
		CALL	do_hline_bwd
		POP	CX
		POP	DI
		POP	DX
		POP	AX
		RET

line_and_plot	ENDP
;...e
;...srect_guts:0:
;
; Do the actual plotting of a rectangle
;
; Input:	BP -> frame
;		(CX,DX) = coordinate of top left
;
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
rect_guts	PROC	NEAR

		MOV	BL,CS:[update_page]
		CALL	calc_page_seg
		MOV	AX,[BP+col]
		CALL	calc_addr	; ES:DI -> address
					; CX = x still

		MOV	BX,[BP+y1]
rg_line:	MOV	CX,[BP+x1]
		MOV	DX,[BP+x2]
		PUSH	AX
		PUSH	BX		; store y
		PUSH	DI		; store screen address
		SUB	DX,CX		; DX = no. pixels left
		INC	DX
		AND	CL,003H		; CL = x coordinate AND 3
		MOV	CH,0C0H
		SHR	CH,CL
		SHR	CH,CL		; CH = positive mask
		CALL	do_hline_fwd	; do a line on the screen
		POP	DI		; restore screen address
		POP	BX		; restore y
		POP	AX
		XCHG	AL,AH
		TEST	DI,2000H
		JNZ	rg_odd
		ADD	DI,2000H
		JMP	SHORT rg_3
rg_odd:		ADD	DI,-2000H + 80
rg_3:		INC	BX		; have moved down a line
		CMP	BX,[BP+y2]	; compare y2 - while not all lines done
		JBE	rg_line
		RET

rect_guts	ENDP
;...e
;...sdo_hline_fwd:0:
;
; Input:	CH=+ve mask, CL = x AND 3,
;		DX=no of pixels, ES:DI -> screen, AX=colour
;
do_hline_fwd	PROC	NEAR

;
; the process of doing one line consists of :-
;
;	while ( !byte_boundary || finished )
;   		plot_pixel;
;	if ( !finished )
;		{
;		while ( > 4 to go )
;			plot_4_pixels_in_one_go;
;		plot_whats_left;
;		}
;

		AND	DX,DX		; zero pixels left?
		JZ	dhzf_done	; if so, must have been a short line

		TEST	CL,003H		; on a byte boundary?
		JZ	dhzf_byte_bound	; yes, start doing optimised writes

dhzf_again:	MOV	AH,AL		; take a copy of the colour
		AND	AH,CH		; extract relevant part of colour
		NOT	CH		; create reverse mask
		AND	ES:[DI],CH	; remove old pixel
		OR	ES:[DI],AH	; add in new pixel
		NOT	CH		; create positive mask

		ROR	CH,1
		ROR	CH,1		; move right
		INC	CL
		DEC	DX		; one less pixel to draw
		JZ	dhzf_done	; if at least one left to draw loop
		TEST	CL,003H		; on the byte boundary yet?
		JNZ	dhzf_again	; do another pixel
		INC	DI		; move on from first byte

dhzf_byte_bound:
		MOV	CX,DX
		SHR	CX,1
		SHR	CX,1		; CX = number of bytes to fill
		REP	STOSB		; store 4 pixels in one go
		AND	DL,003H		; any pixels still left to draw?
		JZ	dhzf_done
		MOV	CL,DL
		MOV	DX,0FF00H
		SHR	DX,CL
		SHR	DX,CL		; create mask for whats left
		AND	AL,DL		; create pixels
		NOT	DL
		AND	ES:[DI],DL	; remove current pixels
		OR	ES:[DI],AL	; add in new pixels
dhzf_done:	RET

do_hline_fwd	ENDP
;...e
;...sdo_hline_bwd:0:
;
; Input:	CH=+ve mask, CL = x AND 3,
;		DX=no of pixels, ES:DI -> screen, AX=colour
;
do_hline_bwd	PROC	NEAR

		AND	DX,DX		; zero pixels left?
		JZ	dhzb_done	; if so, must have been a short line

		AND	CL,003H
		CMP	CL,003H		; on a byte boundary?
		JZ	dhzb_byte_bound	; yes, start doing optimised writes

dhzb_again:	MOV	AH,AL		; take a copy of the colour
		AND	AH,CH		; extract relevant part of colour
		NOT	CH		; create reverse mask
		AND	ES:[DI],CH	; remove old pixel
		OR	ES:[DI],AH	; add in new pixel
		NOT	CH		; create positive mask

		ROL	CH,1
		ROL	CH,1		; move left
		DEC	CL
		DEC	DX		; one less pixel to draw
		JZ	dhzb_done	; if at least one left to draw loop
		AND	CL,003H
		CMP	CL,003H		; on the byte boundary yet?
		JNZ	dhzb_again	; do another pixel
		DEC	DI		; move on from first byte

dhzb_byte_bound:
		MOV	CX,DX
		SHR	CX,1
		SHR	CX,1		; CX = number of bytes to fill
		STD
		REP	STOSB		; store 4 pixels in one go
		CLD
		AND	DL,003H		; any pixels still left to draw?
		JZ	dhzb_done
		MOV	CL,DL
		MOV	DX,000FFH
		SHL	DX,CL
		SHL	DX,CL		; create mask for whats left
		AND	AL,DH		; create pixels
		NOT	DH
		AND	ES:[DI],DH	; remove current pixels
		OR	ES:[DI],AL	; add in new pixels
dhzb_done:	RET

do_hline_bwd	ENDP
;...e

;...svoid pascal g_l_plot\40\d\44\ x\44\ y\44\ col\41\:0:
;
; void pascal g_l_plot(d, x, y, col)
;
d		= 12
y		= 10
x		= 8
col		= 6
;
g_l_plot	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES
		MOV	CX,[BP+x]
		MOV	DX,[BP+y]
		MOV	AX,[BP+col]

		PUSH	DS
		MOV	BX,SEG data_seg
		MOV	DS,BX
		MOV	SI,[item_ptr]
		MOV	[SI+item_type],t_plot
		MOV	BX,[BP+d]
		CALL	store_dist
		MOV	[SI+item_min_x],CX
		MOV	[SI+item_max_x],CX
		MOV	[SI+item_min_y],DX
		MOV	[SI+item_max_y],DX
		ADD	SI,TYPE item
		MOV	[item_ptr],SI
		POP	DS

		CALL	plot_guts
		POP	ES
		POP	BP
		RET	8

g_l_plot	ENDP
;...e
;...svoid pascal g_l_line\40\d\44\ x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_l_line(d, x1, y1, x2, y2, col)
;
d		= 16
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_l_line	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES

		PUSH	DS
		MOV	BX,SEG data_seg
		MOV	DS,BX
		MOV	SI,[item_ptr]
		MOV	[SI+item_type],t_line
		MOV	BX,[BP+d]
		CALL	store_dist

		MOV	CX,[BP+x1]
		MOV	[SI+item_x1],CX
		MOV	DX,[BP+x2]
		MOV	[SI+item_x2],DX
		CMP	CX,DX
		JC	g_l_l_2
		XCHG	CX,DX
g_l_l_2:	MOV	[SI+item_min_x],CX
		MOV	[SI+item_max_x],DX

		MOV	CX,[BP+y1]
		MOV	[SI+item_y1],CX
		MOV	DX,[BP+y2]
		MOV	[SI+item_y2],DX
		CMP	CX,DX
		JC	g_l_l_4
		XCHG	CX,DX
g_l_l_4:	MOV	[SI+item_min_y],CX
		MOV	[SI+item_max_y],DX

		ADD	SI,TYPE item
		MOV	[item_ptr],SI
		POP	DS

		MOV	AX,OFFSET plot
		CALL	line_guts
		POP	ES
		POP	BP
		RET	12

g_l_line	ENDP
;...e
;...svoid pascal g_l_diag_vert\40\d\44\ x\44\ x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_l_diag_vert(d, x1, y1, x2, y2, x, col)
;
d		= 18
x		= 16
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_l_diag_vert	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES

		PUSH	DS
		MOV	BX,SEG data_seg
		MOV	DS,BX
		MOV	SI,[item_ptr]
		MOV	[SI+item_type],t_diag_vert
		MOV	BX,[BP+d]
		CALL	store_dist

		MOV	CX,[BP+x1]
		MOV	[SI+item_x1],CX
		MOV	DX,[BP+x2]
		MOV	[SI+item_x2],DX
		CMP	CX,DX
		JC	gldv_2
		MOV	CX,DX
gldv_2:		MOV	[SI+item_min_x],CX
		MOV	DX,[BP+x]
		MOV	[SI+item_max_x],DX

		MOV	CX,[BP+y1]
		MOV	[SI+item_y1],CX
		MOV	DX,[BP+y2]
		MOV	[SI+item_y2],DX
		CMP	CX,DX
		JC	gldv_4
		XCHG	CX,DX
gldv_4:		MOV	[SI+item_min_y],CX
		MOV	[SI+item_max_y],DX

		ADD	SI,TYPE item
		MOV	[item_ptr],SI
		POP	DS

		MOV	AX,[BP+x]
		MOV	CS:[x_lim],AX
		MOV	CS:[last_filled],-1
		MOV	AX,OFFSET plot_and_line
		CALL	line_guts
		POP	ES
		POP	BP
		RET	14

g_l_diag_vert	ENDP
;...e
;...svoid pascal g_l_vert_diag\40\d\44\ x\44\ x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_l_vert_diag(x1, y1, x2, y2, x, col)
;
d		= 18
x		= 16
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_l_vert_diag	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES

		PUSH	DS
		MOV	BX,SEG data_seg
		MOV	DS,BX
		MOV	SI,[item_ptr]
		MOV	[SI+item_type],t_vert_diag
		MOV	BX,[BP+d]
		CALL	store_dist

		MOV	CX,[BP+x1]
		MOV	[SI+item_x1],CX
		MOV	DX,[BP+x2]
		MOV	[SI+item_x2],DX
		CMP	CX,DX
		JC	glvd_2
		MOV	DX,CX
glvd_2:		MOV	CX,[BP+x]
		MOV	[SI+item_min_x],CX
		MOV	[SI+item_max_x],DX

		MOV	CX,[BP+y1]
		MOV	[SI+item_y1],CX
		MOV	DX,[BP+y2]
		MOV	[SI+item_y2],DX
		CMP	CX,DX
		JC	glvd_4
		XCHG	CX,DX
glvd_4:		MOV	[SI+item_min_y],CX
		MOV	[SI+item_max_y],DX

		ADD	SI,TYPE item
		MOV	[item_ptr],SI
		POP	DS

		MOV	AX,[BP+x]
		MOV	CS:[x_lim],AX
		MOV	CS:[last_filled],-1
		MOV	AX,OFFSET line_and_plot
		CALL	line_guts
		POP	ES
		POP	BP
		RET	14

g_l_vert_diag	ENDP
;...e
;...svoid pascal g_l_rect\40\d\44\ x1\44\ y1\44\ x2\44\ y2\44\ col\41\:0:
;
; void pascal g_l_rect(d, x1, y1, x2, y2, col)
;
d		= 16
x1		= 14
y1		= 12
x2		= 10
y2		= 8
col		= 6
;
g_l_rect	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	ES

		MOV	CX,[BP+x1]	; ensure x1 < x2
		MOV	AX,[BP+x2]
		CMP	CX,AX
		JC	g_l_rect_x_ok
		XCHG	AX,CX
		MOV	[BP+x1],CX
		MOV	[BP+x2],AX
g_l_rect_x_ok:
		MOV	DX,[BP+y1]	; ensure y1 < y2
		MOV	AX,[BP+y2]
		CMP	DX,AX
		JC	g_l_rect_y_ok
		XCHG	AX,DX
		MOV	[BP+y1],DX
		MOV	[BP+y2],AX
g_l_rect_y_ok:

		PUSH	DS
		MOV	BX,SEG data_seg
		MOV	DS,BX
		MOV	SI,[item_ptr]
		MOV	[SI+item_type],t_rect
		MOV	BX,[BP+d]
		CALL	store_dist
		MOV	[SI+item_min_x],CX
		MOV	AX,[BP+x2]
		MOV	[SI+item_max_x],AX
		MOV	[SI+item_min_y],DX
		MOV	AX,[BP+y2]
		MOV	[SI+item_max_y],AX
		ADD	SI,TYPE item
		MOV	[item_ptr],SI
		POP	DS

		CALL	rect_guts
		POP	ES
		POP	BP
		RET	12

g_l_rect	ENDP
;...e
;...svoid pascal g_clear_log\40\\41\:0:
;
; void pascal g_clear_log()
;
g_clear_log	PROC	FAR

		PUSH	DS
		MOV	AX,SEG data_seg
		MOV	DS,AX
		MOV	AX,OFFSET item_table
		MOV	[item_ptr],AX
		POP	DS
		RET

g_clear_log	ENDP
;...e

;...svoid pascal g_clear_spr\40\\41\:0:
;
; void g_clear_spr()
;
g_clear_spr	PROC	FAR

		PUSH	DS
		MOV	AX,SEG data_seg
		MOV	DS,AX
		MOV	AX,OFFSET precalc_table
		MOV	[precalc_ptr],AX
		MOV	AX,OFFSET refresh_table
		MOV	[refresh_ptr],AX
		POP	DS
		RET

g_clear_spr	ENDP
;...e
;...svoid pascal g_precalc\40\d\44\ z\44\ sprite\44\ x\44\ y\41\:0:
;
; void pascal g_precalc(d, z, sprite, x, y)
;
d		= 14
z		= 12
sprite		= 10
x		= 8
y		= 6
;
g_precalc	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	DS
		PUSH	ES

; select sprite to display

		MOV	AX,[BP+sprite]
		XCHG	AL,AH		; * 256
		SHR	AX,1		; * 128
		MOV	SI,AX
		ADD	SI,OFFSET spr_data
		MOV	AX,SEG data_seg
		MOV	DS,AX		; DS:SI ->  unshifted sprite info
		MOV	ES,AX

		MOV	DX,[BP+x]
		CALL	shift_sprite

; calculate mask address

		MOV	CX,[BP+x]
		MOV	DX,[BP+y]
		MOV	BX,SEG data_seg
		CALL	calc_addr	; ES:DI -> mask, CX = x still

		PUSH	DI

; setup even and odd steps

		MOV	CX,even_step - 5
		MOV	DX,odd_step - 5
		TEST	DI,2000H
		JZ	gps_2
		XCHG	CX,DX
gps_2:

; now copy sprite mask to mask plane

		MOV	SI,OFFSET shifted_data

REPT	8
		MOVSW
		MOVSW
		MOVSB
		ADD	DI,CX
		MOVSW
		MOVSW
		MOVSB
		ADD	DI,DX
ENDM

		PUSH	CX		; preserve step 1
		PUSH	DX		; preserve step 2

; consider items obscuring it

		MOV	BX,[BP+d]
		MOV	CX,[BP+x]
		MOV	DX,[BP+y]
		CALL	redraw_in_front

		PUSH	DS
		POP	ES		; ES = DS -> data_seg

; now keep the mask etc. for the drawing phase

		POP	DX		; restore step 2
		POP	CX		; restore step 1
		MOV	SI,[precalc_ptr]; SI -> entry to fill with sprite
		MOV	AX,[BP+z]
		MOV	[SI+precalc_d],AX
		MOV	AX,[BP+x]
		MOV	[SI+precalc_x],AX
		MOV	AX,[BP+y]
		MOV	[SI+precalc_y],AX
		POP	DI		; DS:DI -> mask address
					; screen:DI -> screen address
		MOV	[SI+precalc_spos],DI
		MOV	BL,CS:[update_page]
		CALL	calc_page_seg
		MOV	[SI+precalc_sseg],BX

		ADD	SI,precalc_mask
		XCHG	DI,SI		; SI -> mask, DI -> store for it

		MOV	BX,OFFSET shifted_data + 80
					; BX -> sprite colours

; keep copy of calculated -ve mask and sprite AND +ve mask

REPT	8

REPT	5
		LODSB			; AL = -ve mask
		MOV	AH,AL		; AH = -ve mask
		NOT	AH		; AH = +ve mask
		AND	AH,[BX]		; AH = +ve mask AND sprite
		INC	BX		; advance to next byte
		STOSW
ENDM
		ADD	SI,CX

REPT	5
		LODSB			; AL = -ve mask
		MOV	AH,AL		; AH = -ve mask
		NOT	AH		; AH = +ve mask
		AND	AH,[BX]		; AH = +ve mask AND sprite
		INC	BX		; advance to next byte
		STOSW
ENDM
		ADD	SI,DX
ENDM

		MOV	[precalc_ptr],DI

		POP	ES
		POP	DS
		POP	BP
		RET	10

g_precalc	ENDP
;
; SI -> sprite source
; DX = x coordinate
;
shift_sprite	PROC	NEAR

		AND	DX,3
		MOV	DI,OFFSET shifted_data
;
; Shift the -ve mask
;
		MOV	CX,16
ss_2:		PUSH	CX
		PUSH	DX
		MOV	CX,DX		; get no of times to shift
		LODSW
		XCHG	AH,AL
		MOV	BX,[SI]
		XCHG	BH,BL
		MOV	DH,0FFH
		ADD	SI,2		; row in AX:BX:DH
		JCXZ	ss_5
ss_4:		STC
		RCR	AX,1
		RCR	BX,1
		RCR	DH,1
		STC
		RCR	AX,1
		RCR	BX,1
		RCR	DH,1
		LOOP	ss_4
ss_5:		XCHG	AH,AL
		STOSW
		XCHG	BH,BL
		MOV	[DI],BX
		MOV	[DI+2],DH
		ADD	DI,3
		POP	DX
		POP	CX
		LOOP	ss_2
;
; Shift the -ve mask
;
		MOV	CX,16
ss_6:		PUSH	CX
		PUSH	DX
		MOV	CX,DX		; get no of times to shift
		LODSW
		XCHG	AH,AL
		MOV	BX,[SI]
		XCHG	BH,BL
		XOR	DH,DH
		ADD	SI,2		; row in AX:BX:DH
		JCXZ	ss_9
ss_8:		SHR	AX,1
		RCR	BX,1
		RCR	DH,1
		SHR	AX,1
		RCR	BX,1
		RCR	DH,1
		LOOP	ss_8
ss_9:		XCHG	AH,AL
		STOSW
		XCHG	BH,BL
		MOV	[DI],BX
		MOV	[DI+2],DH
		ADD	DI,3
		POP	DX
		POP	CX
		LOOP	ss_6
		RET

shift_sprite	ENDP
;...e
;...svoid pascal g_sprites\40\\41\:0:
;
; void pascal g_sprites()
;
; Output all the precalculated sprites in order of depth
;
g_sprites	PROC	FAR

		PUSH	DS
		PUSH	ES

		MOV	AX,SEG data_seg
		MOV	DS,AX

g_sprites_loop:	CALL	make_a_pass	; returns CF if displayed a sprite
		JC	g_sprites_loop

		POP	ES
		POP	DS
		RET

g_sprites	ENDP
;
;
make_a_pass	PROC	NEAR

		XOR	DX,DX		; furthest value of d set silly
		XOR	SI,SI
		MOV	DI,OFFSET precalc_table
map_next:	CMP	DI,[precalc_ptr]
		JZ	map_done	; got to the end of the table
		MOV	AX,[DI+precalc_d]
					; get 'distance' of sprite
		CMP	AX,-1		; -1 flag indicates already displayed
		JZ	map_advance	; if already displayed consider next
		CMP	AX,DX		; is this sprite further than furthest
		JB	map_advance	; if not then skip
		MOV	SI,DI		; remember furthest
		MOV	DX,AX		; remember distance of furthest

map_advance:	ADD	DI,TYPE precalc	; advance to next sprite
		JMP	map_next

map_done:	AND	SI,SI		; did we find any sprites
		JZ	map_all_done	; if not then return error

; We now know the sprite that is furthest back, and therefore should
; be displayed first so that it can be partially obliterated by closer
; sprites.

		MOV	[SI+precalc_d],-1
		CALL	do_sprite
		STC			; signal sprite displayed
		RET

map_all_done:	CLC			; signal failed to write a sprite
		RET

make_a_pass	ENDP
;
;
do_sprite	PROC	NEAR

		MOV	DI,[refresh_ptr]
		MOV	AX,[SI+precalc_x]
		MOV	[DI+refresh_x],AX
		MOV	AX,[SI+precalc_y]
		MOV	[DI+refresh_y],AX
		MOV	BX,[SI+precalc_spos]
		MOV	[DI+refresh_addr],BX
		ADD	DI,TYPE refresh
		MOV	[refresh_ptr],DI
		MOV	ES,[SI+precalc_sseg]
		MOV	DI,BX
		ADD	SI,precalc_mask	; DS:SI -> mask and sprite
					; ES:DI -> screen

; setup even and odd steps

		MOV	CX,even_step - 5
		MOV	DX,odd_step - 5
		TEST	DI,2000H
		JZ	gs_2
		XCHG	CX,DX
gs_2:

REPT	8

REPT	5
		LODSW			; AL = -ve mask
					; AH = +ve mask AND sprite
		AND	AL,ES:[DI]	; AL = -ve mask AND screen
		OR	AL,AH
		STOSB
ENDM
		ADD	DI,CX

REPT	5
		LODSW			; AL = -ve mask
					; AH = +ve mask AND sprite
		AND	AL,ES:[DI]	; AL = -ve mask AND screen
		OR	AL,AH
		STOSB
ENDM
		ADD	DI,DX
ENDM

		RET

do_sprite	ENDP
;...e
;...svoid pascal g_unsprite\40\page_from\44\ page_to\44\ x\44\ y\41\:0:
;
; void pascal g_unsprite(page_from, page_to, x, y)
;
page_from	= 12
page_to		= 10
x		= 8
y		= 6
;
g_unsprite	PROC	FAR

		PUSH	BP
		MOV	BP,SP
		PUSH	DS
		PUSH	ES

		MOV	AX,SEG data_seg
		MOV	DS,AX
		MOV	SI,[refresh_ptr]
		MOV	CX,[BP+x]
		MOV	[SI+refresh_x],CX
		MOV	DX,[BP+y]
		MOV	[SI+refresh_y],DX

		MOV	BX,[BP+page_to]
		CALL	calc_page_seg
		CALL	calc_addr	; ES:DI -> place to write to
		MOV	[SI+refresh_addr],DI
		ADD	SI,TYPE refresh
		MOV	[refresh_ptr],SI
		MOV	SI,DI		; DS:SI -> place to write from

		MOV	BX,[BP+page_from]
		CALL	calc_page_seg
		MOV	DS,BX		; DS = segment of page_from

		MOV	CX,even_step - 5
		MOV	DX,odd_step - 5
		TEST	DI,2000H
		JZ	gus_2
		XCHG	CX,DX
gus_2:

REPT	8
		MOVSW
		MOVSW
		MOVSB
		ADD	SI,CX
		MOV	DI,SI
		MOVSW
		MOVSW
		MOVSB
		ADD	SI,DX
		MOV	DI,SI
ENDM

		POP	ES
		POP	DS
		POP	BP
		RET	8

g_unsprite	ENDP
;...e
;...svoid pascal g_refresh\40\page_from\41\:0:
;
; void pascal g_refresh(page_from)
;
; Update those areas of screen that need changing due to sprites
; Do this top down so as to reduce flicker
;
g_refresh	PROC	FAR

		POP	DX
		POP	CX
		POP	BX		; get page_from
		PUSH	CX
		PUSH	DX
		PUSH	DS
		PUSH	ES
		PUSH	BP

		MOV	AX,SEG data_seg
		MOV	DS,AX

		CALL	calc_page_seg
		MOV	BP,BX		; AX = source segment
		MOV	BL,CS:[visible_page]
		CALL	calc_page_seg
		MOV	ES,BX		; ES = destination segment

		MOV	BX,-1
		MOV	[prev_x],BX
		MOV	[prev_y],BX

g_refresh_loop:	CALL	find_highest	; returns ZF if no more
		JZ	g_refresh_done
		MOV	AX,[prev_x]
		CMP	AX,[SI+refresh_x]
		JNZ	g_refresh_do
		MOV	BX,[prev_y]
		CMP	BX,[SI+refresh_y]
		JZ	g_refresh_loop	; done this exact area before
g_refresh_do:	MOV	[prev_x],AX
		MOV	[prev_y],BX
		MOV	[SI+refresh_x],-1
		CALL	refresh_sprite
		JMP	g_refresh_loop

g_refresh_done:	POP	BP
		POP	ES
		POP	DS
		RET

g_refresh	ENDP
;
;
find_highest	PROC	NEAR

		MOV	CX,0FFFFH	; lowest value of x set silly
		MOV	DX,0FFFFH	; lowest value of y set silly
		XOR	SI,SI
		MOV	DI,OFFSET refresh_table
fh_next:	CMP	DI,[refresh_ptr]
		JZ	fh_done		; got to the end of the table
		MOV	AX,[DI+refresh_x]
		CMP	AX,-1		; -1 flag indicates already displayed
		JZ	fh_advance	; if already displayed consider next
		MOV	BX,[DI+refresh_y]
		CMP	BX,DX		; is this sprite higher than highest
		JA	fh_advance	; if lower then forget it
		JB	fh_yes		; if higher then remember it
		CMP	AX,CX		; else consider x's
		JA	fh_advance	; if to the right of highest forget it

fh_yes:		MOV	SI,DI		; remember highest
		MOV	CX,AX		; remember x
		MOV	DX,BX		; remember y

fh_advance:	ADD	DI,TYPE refresh	; advance to next sprite
		JMP	fh_next

fh_done:	AND	SI,SI		; did we find any sprites
		RET

find_highest	ENDP
;
;
refresh_sprite	PROC	NEAR

		PUSH	DS
		MOV	SI,[SI+refresh_addr]
		MOV	DI,SI		; SI = DI -> screen
		MOV	AX,BP
		MOV	DS,AX		; DS:SI -> source, ES:DI -> destination

		MOV	CX,even_step - 5
		MOV	DX,odd_step - 5
		TEST	DI,2000H
		JZ	rs_2
		XCHG	CX,DX
rs_2:

REPT	8
		MOVSW
		MOVSW
		MOVSB
		ADD	SI,CX
		MOV	DI,SI
		MOVSW
		MOVSW
		MOVSB
		ADD	SI,DX
		MOV	DI,SI
ENDM

		POP	DS
		RET

refresh_sprite	ENDP
;...e

;...sstore_dist:0:
;
; Store the distance factor
;
; Input:	BX = distance in yyyyyyyyzzzzxxxx format
;
store_dist	PROC	NEAR

		MOV	[SI+item_dy],BH
		MOV	BH,BL
		AND	BH,0F0H
		MOV	[SI+item_dz],BH
		AND	BL,00FH
		MOV	[SI+item_dx],BL
		RET

store_dist	ENDP
;...e
;...sclip_test:0:
;
; Determine whether clipping is to apply
;
; Input:	BX = sprite distance factor in yyyyyyyyzzzzxxxx format
;		[SI+item_d?], ? = x,y,z set up
; Output:	CF set if should clip
;
;	RULE:	if z(item) < z(sprite) then clip
;		else if z(item) > z(sprite) then dont
;		else if x(item) < x(sprite) and
;			y(item) < y(sprite) then clip
;		else dont
;
clip_test	PROC	NEAR

		MOV	AL,BL
		AND	AL,0F0H
		CMP	[SI+item_dz],AL
		JNZ	ct_x
		MOV	AL,BL
		AND	AL,00FH
		CMP	[SI+item_dx],AL
		JNC	ct_x
		CMP	[SI+item_dy],BH
ct_x:		RET

clip_test	ENDP
;...e
;...sredrawing in the mask:0:
;
; Redraw bits that are in front of the area we are interested in
;
; Input:	(CX,DX) = top left of sprite, BX = d
;
redraw_in_front	PROC	NEAR

		PUSH	DS
		MOV	AX,SEG data_seg
		MOV	DS,AX
		MOV	SI,OFFSET item_table
r_i_f_next:	CMP	SI,[item_ptr]
		JZ	r_i_f_done
		CALL	clip_test
		JNC	r_i_f_advance	; no clipping
		CMP	CX,[SI+item_max_x]
		JA	r_i_f_advance	; sprite is beyond right edge
		CMP	DX,[SI+item_max_y]
		JA	r_i_f_advance	; sprite is below bottom edge
		MOV	AX,CX
		ADD	AX,16
		CMP	AX,[SI+item_min_x]
		JB	r_i_f_advance	; sprite is before left edge
		MOV	AX,DX
		ADD	AX,16
		CMP	AX,[SI+item_min_y]
		JB	r_i_f_advance	; sprite is above top edge
		PUSH	BX
		PUSH	CX
		PUSH	DX
		PUSH	SI
		MOV	AL,[SI+item_type]
		SHL	AL,1
		XOR	AH,AH
		MOV	DI,OFFSET jmp_tab
		ADD	DI,AX
		CALL	[DI]
		POP	SI
		POP	DX
		POP	CX
		POP	BX
r_i_f_advance:	ADD	SI,TYPE item
		JMP	r_i_f_next

r_i_f_done:	POP	DS
		RET

redraw_in_front	ENDP
;
;
mask_plot	PROC	NEAR

		MOV	CX,[SI+item_min_x]
		MOV	DX,[SI+item_min_y]
		CALL	m_plot_guts
		RET

mask_plot	ENDP
;
mask_line	PROC	NEAR

		POP	DX
		PUSH	[SI+item_x1]
		PUSH	[SI+item_y1]
		PUSH	[SI+item_x2]
		PUSH	[SI+item_y2]
		PUSH	DX
		PUSH	BP
		MOV	BP,SP
		SUB	BP,2		; no return address segment
		MOV	AX,OFFSET m_plot
		CALL	m_line_guts
		POP	BP
		RET	8

mask_line	ENDP
;
mask_diag_vert	PROC	NEAR

		POP	DX
		MOV	AX,[SI+item_max_x]
		MOV	CS:[x_lim],AX
		MOV	CS:[last_filled],-1
		PUSH	[SI+item_x1]
		PUSH	[SI+item_y1]
		PUSH	[SI+item_x2]
		PUSH	[SI+item_y2]
		PUSH	DX
		PUSH	BP
		MOV	BP,SP
		SUB	BP,2		; no return address segment
		MOV	AX,OFFSET m_plot_and_line
		CALL	m_line_guts
		POP	BP
		RET	8

mask_diag_vert	ENDP
;
mask_vert_diag	PROC	NEAR

		POP	DX
		MOV	AX,[SI+item_min_x]
		MOV	CS:[x_lim],AX
		MOV	CS:[last_filled],-1
		PUSH	[SI+item_x1]
		PUSH	[SI+item_y1]
		PUSH	[SI+item_x2]
		PUSH	[SI+item_y2]
		PUSH	DX
		PUSH	BP
		MOV	BP,SP
		SUB	BP,2		; no return address segment
		MOV	AX,OFFSET m_line_and_plot
		CALL	m_line_guts
		POP	BP
		RET	8

mask_vert_diag	ENDP
;
mask_rect	PROC	NEAR

		POP	BX
		MOV	CX,[SI+item_min_x]
		MOV	DX,[SI+item_min_y]
		PUSH	CX
		PUSH	DX
		PUSH	[SI+item_max_x]
		PUSH	[SI+item_max_y]
		PUSH	BX
		PUSH	BP
		MOV	BP,SP
		SUB	BP,2		; no return address segment
		CALL	m_rect_guts
		POP	BP
		RET	8

mask_rect	ENDP
;...e

;...sm_plot_guts:0:
;
; The actual guts of plotting onto the mask
;
; Input:	(CX,DX) = coordinates
;
m_plot_guts	PROC	NEAR

		MOV	BX,SEG data_seg
		CALL	calc_addr	; ES:DI -> address
					; CX still = x

		AND	CL,003H		; number of times to shift colour
					; and mask right
		SHL	CL,1

		MOV	CH,0C0H		; mask for plotting
		SHR	CH,CL		; positioned in correct place
		OR	ES:[DI],CH	; set the pixel
		RET

m_plot_guts	ENDP
;...e
;...sm_line_guts:0:
;
; The main Bresenham line drawing algorithm
;
; Input:	AX --> plotting routine
;		BP --> a frame holding vars below
;
x1		= 12
y1		= 10
x2		= 8
y2		= 6
;
; Uses:		ES:DI -> address, CH = positive mask, AX = col
;
m_line_guts	PROC	NEAR

		MOV	CS:[plotter],AX

; calculate screen position

		MOV	CX,[BP+x1]
		MOV	CS:[current_x],CX
		MOV	DX,[BP+y1]
		MOV	CS:[current_y],DX
		MOV	BX,SEG data_seg	; BX = segment
		CALL	calc_addr	; ES:DI -> address, AX = crap
					; CX = x still

		AND	CL,003H
		SHL	CL,1
		MOV	CH,0C0H
		SHR	CH,CL
		MOV	CS:[bitmask],CH

; set up stepping for the right directions

		MOV	DX,[BP+x1]
		SUB	DX,[BP+x2]
		JG	m_line_2	; jump if x1 > x2
		NEG	DX
		MOV	SI,OFFSET step_right
		JMP	SHORT m_line_4
m_line_2:	MOV	SI,OFFSET step_left
m_line_4:
					; DX = |xdiff|
					; SI = horizontal stepper

		MOV	BX,[BP+y1]
		SUB	BX,[BP+y2]
		JG	m_line_6	; jump if y1 > y2
		NEG	BX
		MOV	CX,OFFSET step_down
		JMP	SHORT m_line_8
m_line_6:	MOV	CX,OFFSET step_up
m_line_8:
					; BX = |ydiff|
					; CX = vertical stepper

; determine whether x or y changes more rapidly

		CMP	DX,BX
		JG	m_line_20	; jump if x varys most
		XCHG	BX,DX		; DX = major side, BX = minor side
		XCHG	CX,SI		; SI = always stepper
					; CX = sometimes stepper

m_line_20:	MOV	CS:[step_always],SI
		MOV	CS:[step_sometimes],CX

		CALL	CS:[plotter]
		XOR	SI,SI		; clear total
		MOV	CX,DX
		JCXZ	m_line_30	; line is of zero length
m_line_24:	ADD	SI,BX
		SAL	SI,1
		CMP	SI,DX
		JNG	m_line_26
		SAR	SI,1
		SUB	SI,DX
		CALL	CS:[step_sometimes]
		JMP	SHORT m_line_28
m_line_26:	SAR	SI,1
m_line_28:	CALL	CS:[step_always]
		CALL	CS:[plotter]
		LOOP	m_line_24

m_line_30:	RET

m_line_guts	ENDP
;...e
;...sm_plot:0:
;
; Plotting utility designed for use with line drawer on the mask
;
m_plot		PROC	NEAR

		MOV	AH,CS:[bitmask]
		OR	ES:[DI],AH
		RET

m_plot		ENDP
;...e
;...sm_plot_and_line:0:
;
; Plotter with option of drawing a line to a column for line drawer for mask
;
m_plot_and_line	PROC	NEAR

		PUSH	DX
		MOV	DX,CS:[current_y]
		CMP	DX,CS:[last_filled]
		JNZ	mpal_do_it
		MOV	AH,CS:[bitmask]
		OR	ES:[DI],AH
		POP	DX
		RET

mpal_do_it:	MOV	CS:[last_filled],DX
		PUSH	DI
		PUSH	CX
		MOV	CX,CS:[current_x]
		MOV	DX,CS:[x_lim]
		SUB	DX,CX
		INC	DX
		MOV	CH,CS:[bitmask]
		CALL	m_do_hline_fwd
		POP	CX
		POP	DI
		POP	DX
		RET

m_plot_and_line	ENDP
;...e
;...sm_line_and_plot:0:
;
; Plotter with option of drawing a line from a column for line drawer for mask
;
m_line_and_plot	PROC	NEAR

		PUSH	DX
		MOV	DX,CS:[current_y]
		CMP	DX,CS:[last_filled]
		JNZ	mlap_do_it
		MOV	AH,CS:[bitmask]
		OR	ES:[DI],AH
		POP	DX
		RET

mlap_do_it:	MOV	CS:[last_filled],DX
		PUSH	DI
		PUSH	CX
		MOV	CX,CS:[current_x]
		MOV	DX,CS:[x_lim]
		SUB	DX,CX
		NEG	DX
		INC	DX
		MOV	CH,CS:[bitmask]
		CALL	m_do_hline_bwd
		POP	CX
		POP	DI
		POP	DX
		RET

m_line_and_plot	ENDP
;...e
;...sm_rect_guts:0:
;
; Do the actual plotting of a rectangle onto a mask
;
; Input:	BP -> frame
;		(CX,DX) = coordinate of top left
;
x1		= 12
y1		= 10
x2		= 8
y2		= 6
;
m_rect_guts	PROC	NEAR

		MOV	BX,SEG data_seg
		CALL	calc_addr	; ES:DI -> address
					; CX = x still

		MOV	BX,[BP+y1]
mrg_line:	MOV	CX,[BP+x1]
		MOV	DX,[BP+x2]
		PUSH	BX		; store y
		PUSH	DI		; store screen address
		SUB	DX,CX		; DX = no. pixels left
		INC	DX
		AND	CL,003H		; CL = x coordinate AND 3
		MOV	CH,0C0H
		SHR	CH,CL
		SHR	CH,CL		; CH = positive mask
		CALL	m_do_hline_fwd	; do a line on the screen
		POP	DI		; restore screen address
		POP	BX		; restore y
		TEST	DI,2000H
		JNZ	mrg_odd
		ADD	DI,2000H
		JMP	SHORT mrg_3
mrg_odd:	ADD	DI,-2000H + 80
mrg_3:		INC	BX		; have moved down a line
		CMP	BX,[BP+y2]	; compare y2 - while not all lines done
		JBE	mrg_line
		RET

m_rect_guts	ENDP
;...e
;...sm_do_hline_fwd:0:
;
; This is for writing to a mask
;
; Input:	CH=+ve mask, CL = x AND 3
;		DX=no of pixels, ES:DI -> screen
;
m_do_hline_fwd	PROC	NEAR

		AND	DX,DX		; zero pixels left?
		JZ	mdhzf_done	; if so, must have been a short line

		TEST	CL,003H		; on a byte boundary?
		JZ	mdhzf_byte_bound
					; yes, start doing optimised writes

mdhzf_again:	OR	ES:[DI],CH	; set pixel
		ROR	CH,1
		ROR	CH,1		; move right
		INC	CL
		DEC	DX		; one less pixel to draw
		JZ	mdhzf_done	; if at least one left to draw loop
		TEST	CL,003H		; on the byte boundary yet?
		JNZ	mdhzf_again	; do another pixel
		INC	DI		; move on from first byte

mdhzf_byte_bound:
		MOV	AL,0FFH		; set 4 pixels at a time
		MOV	CX,DX
		SHR	CX,1
		SHR	CX,1		; CX = number of bytes to fill
		REP	STOSB		; store 4 pixels in one go
		AND	DL,003H		; any pixels still left to draw?
		JZ	mdhzf_done
		MOV	CL,DL
		MOV	DX,0FF00H
		SHR	DX,CL
		SHR	DX,CL		; create mask for whats left
		OR	ES:[DI],DL	; set pixels
mdhzf_done:	RET

m_do_hline_fwd	ENDP
;...e
;...sm_do_hline_bwd:0:
;
; Input:	CH=+ve mask, CL = x AND 3
;		DX=no of pixels, ES:DI -> screen
;
m_do_hline_bwd	PROC	NEAR

		AND	DX,DX		; zero pixels left?
		JZ	mdhzb_done	; if so, must have been a short line

		AND	CL,003H
		CMP	CL,003H		; on a byte boundary?
		JZ	mdhzb_byte_bound
					; yes, start doing optimised writes

mdhzb_again:	OR	ES:[DI],CH	; set pixel
		ROL	CH,1
		ROL	CH,1		; move left
		DEC	CL
		DEC	DX		; one less pixel to draw
		JZ	mdhzb_done	; if at least one left to draw loop
		AND	CL,003H
		CMP	CL,003H		; on the byte boundary yet?
		JNZ	mdhzb_again	; do another pixel
		DEC	DI		; move on from first byte

mdhzb_byte_bound:
		MOV	AL,0FFH
		MOV	CX,DX
		SHR	CX,1
		SHR	CX,1		; CX = number of bytes to fill
		STD
		REP	STOSB		; store 4 pixels in one go
		CLD
		AND	DL,003H		; any pixels still left to draw?
		JZ	mdhzb_done
		MOV	CL,DL
		MOV	DX,000FFH
		SHL	DX,CL
		SHL	DX,CL		; create mask for whats left
		OR	ES:[DI],DH
mdhzb_done:	RET

m_do_hline_bwd	ENDP
;...e

;...svoid pascal g_sync\40\\41\:0:
;
; void pascal g_sync()
;
g_sync		PROC	FAR

		MOV	DX,03DAH

g_sync_loop:	IN	AL,DX
		AND	AL,00001000B		; get retrace bit
		JNZ	g_sync_loop		; already retracing

g_sync_loop_2:	IN	AL,DX			; get retrace bit
		AND	AL,00001000B		; test retrace bit
		JZ	g_sync_loop_2		; wait for start
		RET

g_sync		ENDP
;...e

;...scalc_addr:0:
;
; Input:	BX = correct segment (CX,DX) = coordinates, AX = colour
; Output:	ES:DI -> relevant byte AL = colour for row, AH = other colour
; Corrupts:	BX,DX
;
calc_addr	PROC	NEAR

		TEST	DL,1
		JZ	calc_addr_1
		XCHG	AL,AH
calc_addr_1:
		MOV	ES,BX
		MOV	BX,CX
		SHR	BX,1
		SHR	BX,1		; BX = (x/4) range 0-79
		SHR	DX,1
		RCR	BH,1
		SHR	BH,1
		SHR	BH,1		; BX = (x/4) + y0*2000H
		XCHG	DH,DL
		SHR	DX,1
		SHR	DX,1
		ADD	BX,DX		; BX = (x/4) + y0*2000H + (y/2)*64
		SHR	DX,1
		SHR	DX,1
		ADD	BX,DX		; BX = (x/4) + y0*2000H + (y/2)*80
		MOV	DI,BX
		RET

calc_addr	ENDP
;...e
;...scalc_char_addr:0:
;
; Input:	BX = segment, (CX,DX) = coordinates in characters
; Output:	ES:DI -> screen address
;
calc_char_addr	PROC	NEAR

		MOV	ES,BX
		XCHG	DL,DH		; DX = y * 256
		MOV	DI,DX
		SHR	DX,1
		SHR	DX,1		; DX = y * 64
		ADD	DI,DX
		SHL	CX,1		; CX = x * 2
		ADD	DI,CX		; DI = y * 320 + y * 2
		RET

calc_char_addr	ENDP
;...e
;...sstepping functions:0:
;
; Stepping functions for use with line drawer
;
step_right	PROC	NEAR

		INC	CS:[current_x]
		TEST	CS:[bitmask],003H
		JNZ	step_right_2	; into a new byte
		SHR	CS:[bitmask],1
		SHR	CS:[bitmask],1
		RET
step_right_2:	MOV	CS:[bitmask],0C0H
		INC	DI
		RET

step_right	ENDP
;
step_left	PROC	NEAR

		DEC	CS:[current_x]
		TEST	CS:[bitmask],0C0H
		JNZ	step_left_2	; into a new byte
		SHL	CS:[bitmask],1
		SHL	CS:[bitmask],1
		RET
step_left_2:	MOV	CS:[bitmask],003H
		DEC	DI
		RET

step_left	ENDP
;
step_down	PROC	NEAR

		INC	CS:[current_y]
		XCHG	AL,AH
		TEST	DI,2000H
		JNZ	step_down_2
		ADD	DI,even_step
		RET
step_down_2:	ADD	DI,odd_step
		RET

step_down	ENDP
;
step_up		PROC	NEAR

		DEC	CS:[current_y]
		XCHG	AL,AH
		TEST	DI,2000H
		JNZ	step_up_2
		ADD	DI,-odd_step
		RET
step_up_2:	ADD	DI,-even_step
		RET

step_up		ENDP
;...e

CGAG_TEXT	ENDS
;...e
END
